{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8cc39cad",
   "metadata": {},
   "source": [
    "## Pairwise Evaluation\n",
    "\n",
    "일부 평가에서는 두 개 이상의 LLM 생성물을 서로 비교하고자 합니다.\n",
    "\n",
    "[Chatbot Arena](https://lmsys.org/blog/2023-05-03-arena/) 나 LLM 리더보드에서 어렵지 않게 접할 수 있는 비교 평가 방식입니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "34a30a84",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 설치\n",
    "# !pip install -qU langsmith langchain-teddynote"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d75d1492",
   "metadata": {},
   "outputs": [],
   "source": [
    "# API KEY를 환경변수로 관리하기 위한 설정 파일\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# API KEY 정보로드\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "633d9db2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# LangSmith 추적을 설정합니다. https://smith.langchain.com\n",
    "# !pip install -qU langchain-teddynote\n",
    "from langchain_teddynote import logging\n",
    "\n",
    "# 프로젝트 이름을 입력합니다.\n",
    "logging.langsmith(\"CH16-Evaluations\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d5363c3e",
   "metadata": {},
   "source": [
    "이제 이러한 예제 실행에서 데이터셋을 생성할 수 있습니다.\n",
    "\n",
    "입력만 저장하면 됩니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b2a88355",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "from langchain_core.prompts import PromptTemplate\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "\n",
    "\n",
    "def evaluate_pairwise(runs: list, example) -> dict:\n",
    "    \"\"\"\n",
    "    A simple evaluator for pairwise answers to score based on  engagement\n",
    "    \"\"\"\n",
    "\n",
    "    # 점수 저장\n",
    "    scores = {}\n",
    "    for i, run in enumerate(runs):\n",
    "        scores[run.id] = i\n",
    "\n",
    "    # 각 예제에 대한 실행 쌍\n",
    "    answer_a = runs[0].outputs[\"answer\"]\n",
    "    answer_b = runs[1].outputs[\"answer\"]\n",
    "    question = example.inputs[\"question\"]\n",
    "\n",
    "    # 함수 호출이 있는 LLM, 최고 성능 모델 사용\n",
    "    llm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0)\n",
    "\n",
    "    # 구조화된 프롬프트\n",
    "    grade_prompt = PromptTemplate.from_template(\n",
    "        \"\"\"\n",
    "        You are an LLM judge. Compare the following two answers to a question and determine which one is better.\n",
    "        Better answer is the one that is more detailed and informative.\n",
    "        If the answer is not related to the question, it is not a good answer.\n",
    "        \n",
    "        # Question:\n",
    "        {question}\n",
    "        \n",
    "        #Answer A: \n",
    "        {answer_a}\n",
    "        \n",
    "        #Answer B: \n",
    "        {answer_b}\n",
    "        \n",
    "        Output should be either `A` or `B`. Pick the answer that is better.\n",
    "        \n",
    "        #Preference:\n",
    "        \"\"\"\n",
    "    )\n",
    "    answer_grader = grade_prompt | llm | StrOutputParser()\n",
    "\n",
    "    # 점수 획득\n",
    "    score = answer_grader.invoke(\n",
    "        {\n",
    "            \"question\": question,\n",
    "            \"answer_a\": answer_a,\n",
    "            \"answer_b\": answer_b,\n",
    "        }\n",
    "    )\n",
    "    # score = score[\"Preference\"]\n",
    "\n",
    "    # 점수에 따른 실행 할당 매핑\n",
    "    if score == \"A\":  # Assistant A 선호\n",
    "        scores[runs[0].id] = 1\n",
    "        scores[runs[1].id] = 0\n",
    "    elif score == \"B\":  # Assistant B 선호\n",
    "        scores[runs[0].id] = 0\n",
    "        scores[runs[1].id] = 1\n",
    "    else:\n",
    "        scores[runs[0].id] = 0\n",
    "        scores[runs[1].id] = 0\n",
    "\n",
    "    return {\"key\": \"ranked_preference\", \"scores\": scores}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e8d02855",
   "metadata": {},
   "source": [
    "비교 평가를 수행합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e2add5a8",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langsmith.evaluation import evaluate_comparative\n",
    "\n",
    "# 실험 이름 또는 ID 배열 교체\n",
    "evaluate_comparative(\n",
    "    [\"MODEL_COMPARE_EVAL-e18f80ab\", \"MODEL_COMPARE_EVAL-7b929533\"],\n",
    "    # 평가자 배열\n",
    "    evaluators=[evaluate_pairwise],\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "543bdd61",
   "metadata": {},
   "source": [
    "![](./assets/output-11.png)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-kr-lwwSZlnu-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
